package mirrormonkey.util.annotations.parsing;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import mirrormonkey.util.annotations.control.AnnotationOverride;

/**
 * A <tt>MemberDataIR</tt> that will match constructors with a given signature.<br>
 * 
 * This class will basically collect every constructor and its annotations that
 * has the same signature as the constructor that it was initialized with.
 * 
 * @author Philipp Christian Loewner
 * 
 */
public class ConstructorIR extends MemberIR {

	/**
	 * Least recent constructor
	 */
	protected Constructor<?> collectedConstructor;

	/**
	 * We are only interested in constructors.
	 */
	public static final Class<?>[] COLLECT_CLASSES = { Constructor.class };

	/**
	 * Parameter annotations
	 */
	@SuppressWarnings("rawtypes")
	protected Map[] paramAnnotations;

	/**
	 * Creates a new <tt>ConstructorIR</tt>. If this constructor is called, then
	 * this <tt>ConstructorIR</tt> is the first <tt>MemberDataIR</tt> for a
	 * constructor.
	 * 
	 * @param parser
	 *            the <tt>AnnotationParser</tt> that parses the class hierarchy
	 *            and creates this <tt>ConstructorIR</tt>
	 * @param member
	 *            the <tt>Constructor</tt> that this <tt>ConstructorIR</tt> is
	 *            created for
	 */
	public ConstructorIR(AnnotationParser parser, Constructor<?> member) {
		super(parser, member);
		paramAnnotations = new HashMap<?, ?>[member.getParameterAnnotations().length];
		for (int i = 0; i < member.getParameterAnnotations().length; i++) {
			paramAnnotations[i] = new HashMap<Object, Object>();
		}
	}

	/**
	 * Creates a new <tt>ConstructorIR</tt> and extracts data from a previous IR
	 * that was present for a previous constructor with the same type.
	 * 
	 * @param previous
	 *            previously present <tt>ConstructorIR</tt>
	 * @param member
	 *            the <tt>Constructor</tt> that this <tt>ConstructarIR</tt>
	 *            should be created for.
	 */
	public ConstructorIR(ConstructorIR previous, Constructor<?> member) {
		super(previous, member);
		this.collectedConstructor = previous.collectedConstructor;
		this.paramAnnotations = previous.paramAnnotations;
	}

	@Override
	public boolean matches(Member member) {
		Constructor<?> c = (Constructor<?>) member;
		return Arrays.deepEquals(c.getParameterTypes(),
				collectedConstructor.getParameterTypes());
	}

	/**
	 * Parses <tt>c's</tt> parameter annotations. Basically, all parameter
	 * annotations will be collected. If an <tt>AnnotationOverride</tt> is
	 * detected, every previously collected annotation is lost.
	 * 
	 * @param c
	 *            <tt>Constructor</tt> to parse parameters of
	 */
	@SuppressWarnings("unchecked")
	public void parseParameterAnnotations(Constructor<?> c) {
		Annotation[][] annotations = c.getParameterAnnotations();
		for (int i = 0; i < annotations.length; i++) {
			for (Annotation a : annotations[i]) {
				if (a.annotationType().equals(AnnotationOverride.class)) {
					paramAnnotations[i].clear();
				}
			}
			for (Annotation a : annotations[i]) {
				if (!a.annotationType().equals(AnnotationOverride.class)) {
					paramAnnotations[i].put(a.annotationType(), a);
				}
			}
		}
	}

	/**
	 * Returns an array of <tt>Maps</tt> reflecting the collected annotations
	 * for each parameter.
	 * 
	 * @return <tt>Map</tt> of all collected parameter annotations. This will be
	 *         a <tt>Map<Class<? extends Annotation>, Annotation></tt>, but java
	 *         does not permit returning a generic <tt>Map</tt> array due to
	 *         type consistency issues with generic arrays.
	 */
	@SuppressWarnings("rawtypes")
	public Map[] getParameterAnnotations() {
		return paramAnnotations;
	}

	/**
	 * Since a constructor can only be used to create one specific member class,
	 * the framework has to determine which collected constructors can actually
	 * be used to create the least recent class in the hierarchy.<br>
	 * 
	 * Override this method with <tt>true</tt> if collecting the constructor is
	 * optional.
	 * 
	 * @param clazz
	 *            type of objects that the collected constructor should return
	 * @return <tt>true</tt>, if the collected constructor returns objects of
	 *         the specific type, <tt>false</tt> otherwise
	 */
	@Override
	public boolean isValidFor(Class<?> clazz) {
		return collectedConstructor.getDeclaringClass().equals(clazz);
	}

	@Override
	public void parseField(Field field) {
		throw new RuntimeException(getClass() + ": cannot parse fields.");
	}

	@Override
	public void parseMethod(Method method) {
		throw new RuntimeException(getClass() + ": cannot parse methods.");
	}

	@Override
	public void parseConstructor(Constructor<?> constr) {
		collectedConstructor = constr;
		parseParameterAnnotations(constr);
		parseAnnotatedElement(constr);
	}

	@Override
	public Class<?>[] getListenMemberClasses() {
		return COLLECT_CLASSES;
	}

}
